<?php

namespace mini;

use Exception;
use InvalidArgumentException;

class App
{
    public function __construct()
    {
        self::initConfig();
        $this->init();
        $this->route();
        self::trace();
    }

    private static function initConfig()
    {
        $config_file = ROOT_PATH . 'config/app.php';
        if (file_exists($config_file)) {
            return TRUE;
        }

        file_put_contents($config_file, file_get_contents(FRAME_PATH . 'templates/config.php'));
    }

    /**
     * 环境配置
     */
    private function init()
    {
        Session::start();

        // 显示所有错误
        error_reporting(E_ALL);

        if (APP_DEBUG) {
            // 调试模式，网页显示错误信息
            ini_set('display_errors', '1');
        } else {
            // 线上模式网页不显示错误信息
            error_reporting(0);
            ini_set('display_errors', '0');
            ini_set('log_errors', 'On');
        }

        // 自定义异常
        set_error_handler('mini\App::errorHandler');
        set_exception_handler('mini\App::exceptionHandler');

        // 设置时区
        date_default_timezone_set(config('timezone'));

        self::trace();
    }

    /**
     * 路由
     */
    private function route()
    {
        // 解析请求的URI以获取路由信息
        $routeInfo = $this->parseRequestUriToRouteInfo();
        // 解析并设置路由默认值
        [$app, $controller, $method] = $this->getDefaultRouteValues($routeInfo);

        define('APP_NAME', $app);
        define('CONTROLLER_NAME', $controller);
        define('METHOD_NAME', $method);

        $controllerClass = "app\\{$app}\\controllers\\" . ucfirst($controller);

        $routeArray = [$app, $controller, $method];
        define('URL_SEGMENT', implode('/', $routeArray));

        for ($i = 2; $i < count($routeArray); $i++) {
            if (preg_match('/^page_(.*)()*$/Ui', $routeArray[$i], $matches)) {
                define("PAGE_NUMBER",  intval($matches[1]));
                array_splice($routeArray, $i, 1);
            }
        }

        if (!defined("PAGE_NUMBER")) {
            define("PAGE_NUMBER",  1);
        }

        // dd($controllerClass);

        if (class_exists($controllerClass)) {
            if (method_exists($controllerClass, $method)) {
                // 执行控制器类方法
                (new $controllerClass($method))->{$method}();
            } else {
                abort('404');
            }
        } else {
            // 适当地处理错误，例如抛出异常或显示错误消息
            throw new Exception("Controller class '{$controllerClass}' does not exist.");
        }
    }

    /**
     * 解析请求的URI以获取路由信息
     *
     * @return array|null 返回解析后的路由信息，如果解析失败则返回null
     */
    private function parseRequestUriToRouteInfo()
    {
        $originalUri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        $routeInfo = $this->parseUriToModuleControllerAction($originalUri);

        return $routeInfo;
    }

    /**
     * 解析并设置路由默认值
     *
     * @param array $routeInfo 路由信息数组
     *
     * @return array 包含应用、控制器和方法的数组
     */
    private function getDefaultRouteValues(array $routeInfo)
    {
        $app        = $routeInfo[0] ?? DEFAULT_APP;
        $controller = $routeInfo[1] ?? DEFAULT_CONTROLLER;
        $method     = $routeInfo[2] ?? DEFAULT_METHOD;

        // 控制器文件路径构建
        $controllerFile = APP_PATH . $app . '/controllers/' . ucfirst($controller) . '.php';
        // 检查控制器文件是否存在，同时防止目录遍历攻击
        if (!empty($controller) && strpos($controller, '..') === false && !file_exists($controllerFile)) {
            $controller = DEFAULT_CONTROLLER;
            // 保持用户可能指定的方法，否则使用默认方法
            $method = isset($routeInfo[1]) ? $routeInfo[1] : DEFAULT_METHOD;
        }

        return [$app, $controller, $method];
    }

    /**
     * 解析URI以提取模块、控制器和动作。
     *
     * @param string $uri    请求URI。
     * @param string $prefix 要从URI中去除的前缀（默认为'/'）。
     *
     * @return array 包含模块、控制器和动作（如果可用）的数组。
     */
    private function parseUriToModuleControllerAction(string $uri, string $prefix = '/')
    {
        // 移除URI中的前缀
        $uri = substr($uri, strlen($prefix));

        // 防止目录遍历攻击
        if (strpos($uri, '..') !== false) {
            throw new InvalidArgumentException('无效URI: 检测到潜在的目录遍历。');
        }

        // 移除可能存在的文件扩展名
        [$cleanUri,] = explode('.', $uri, 2);

        // 分割URI成多个部分，并移除空部分
        $segments = array_filter(explode('/', $cleanUri), 'strlen');

        // 返回解析后的部分
        return $segments;
    }

    /**
     * 设置用户自定义的错误处理程序，然后触发错误。
     *
     * @param int    $code     错误编码
     * @param string $message  错误消息
     * @param string $file     引发错误的文件名
     * @param int    $line     引发错误所在行
     *
     * @return void
     */
    public static function errorHandler($level, $message, $file, $line): void
    {
        if (error_reporting() !== 0) {
            throw new \ErrorException($message, 0, $level, $file, $line);
        }
    }

    /**
     * 异常处理
     *
     * @param Exception $exception  异常
     *
     * @return void
     */
    public static function exceptionHandler($exception)
    {
        // Code is 404 (not found) or 500 (general error)
        $code = $exception->getCode();
        if ($code != 404) {
            $code = 500;
        }
        http_response_code($code);

        if (APP_DEBUG) {
            include 'templates/debug.php';
            die();
        } else {
            $log = new Log();
            $log->debug($exception->getMessage() . '\n' . $exception->getFile() . '\n' . $exception->getLine());
            return $code;
        }
    }

    private static function trace()
    {
        // 追踪调试
        $GLOBALS['traceSql'] = [];

        // 开启调试追踪
        if (config('trace')) {
            include __DIR__ . '/templates/trace.php';
        }
    }
}
